using GameFramework;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityGameFramework.Runtime;

namespace LS
{
    public class CoroutineComponent : CustomGameFrameworkComponent
    {
        private Dictionary<object, List<CoroutineHolder>> m_CoroutineHolders = new Dictionary<object, List<CoroutineHolder>>();

        public CoroutineHolder StartCoroutine(object holder, IEnumerator coroutine)
        {
            CoroutineHolder coroutineHolder = Create(holder, coroutine);
            Add(coroutineHolder);
            var coroutineIts = StartCoroutine(Internal_CoroutineRunner(coroutineHolder));
            coroutineHolder.Setup(coroutineIts);
            return coroutineHolder;
        }

        public void StopCoroutine(CoroutineHolder coroutine)
        {
            this.StopCoroutine(coroutine.coroutine);
            Remove(coroutine);
            Release(coroutine);
        }

        public void StopCoroutine(object holder)
        {
            var arr = Remove(holder);
            foreach (var item in arr)
            {
                this.StopCoroutine(item.coroutine);
            }
            Release(arr);
        }

        private CoroutineHolder Create(object holder, IEnumerator coroutine)
        {
            CoroutineHolder coroutineHolder = ReferencePool.Acquire<CoroutineHolder>();
            coroutineHolder.Setup(holder, coroutine);
            Add(coroutineHolder);
            return coroutineHolder;
        }

        private void Release(CoroutineHolder holder)
        {
            ReferencePool.Release(holder);
        }

        private void Release(CoroutineHolder[] holder)
        {
            foreach (var item in holder)
            {
                ReferencePool.Release(item);
            }
        }

        private void Add(CoroutineHolder holder)
        {
            List<CoroutineHolder> holderList = null;
            if (!m_CoroutineHolders.TryGetValue(holder.holder, out holderList))
            {
                holderList = new List<CoroutineHolder>();
                m_CoroutineHolders.Add(holder.holder,holderList);
            }
            holderList.Add(holder);
        }

        private void Remove(CoroutineHolder holder)
        {
            object holderObj = holder.holder;
            if (m_CoroutineHolders.TryGetValue(holderObj, out var list))
            {
                list.Remove(holder);
                if (list.Count <= 0)
                {
                    m_CoroutineHolders.Remove(holderObj);
                }
            }
        }

        private CoroutineHolder[] Remove(object holder)
        {
            CoroutineHolder[] arr = null;
            if (m_CoroutineHolders.TryGetValue(holder, out var list))
            {
                arr = list.ToArray();
                list.Clear();
                m_CoroutineHolders.Remove(holder);
            }
            return arr;
        }

        IEnumerator Internal_CoroutineRunner(CoroutineHolder coroutineHolder)
        {
            CoroutineHolder ch = coroutineHolder;
            object holderObj = coroutineHolder.holder;
            IEnumerator enumerator = coroutineHolder.source;

            yield return enumerator;

            if(!coroutineHolder.Available)
            yield return new WaitUntil(coroutineHolder.IsAvailable);

            Remove(ch);
            Release(ch);
        }

        public class CoroutineHolder : IReference
        {
            internal object holder;
            internal IEnumerator source;
            internal Coroutine coroutine;

            internal bool Available
            {
                get;
                private set;
            }

            internal void Setup(object holder,IEnumerator source)
            {
                this.holder = holder;
                this.source = source;
            }

            internal void Setup(Coroutine coroutine)
            {
                Available = true;
                this.coroutine = coroutine;
            }

            internal bool IsAvailable()
            {
                return Available;
            }

            void IReference.Clear()
            {
                Available = false;
                holder = null;
                source = null;
                coroutine = null;
            }
        }
    }
}
